﻿using System;
using System.ComponentModel;
using System.Linq.Expressions;
using System.Reactive;
using System.Reactive.Linq;
using System.Reflection;
using System.Windows;
using System.Linq;

namespace Present.Core.Tests.Extensions
{
    public static class NotifyOnChangeExtensions
    {
        /// <summary>
        /// Call a delegate when a particualr property changes
        /// </summary>
        public static IObservable<EventPattern<PropertyChangedEventArgs>> WhenPropertyChanges<T>(this T me, Expression<Func<T, object>> property) where T : INotifyPropertyChanged
        {
            return FromPropertyChanged(me, property)
                .Select(e => new EventPattern<PropertyChangedEventArgs>(e.Sender, (PropertyChangedEventArgs)e.EventArgs));
        }



        /// <summary>
        /// Call a delegate when any of a particualr property changes
        /// </summary>
        public static IObservable<EventPattern<PropertyChangedEventArgs>> WhenPropertiesChange<T>(this T me, params Expression<Func<T, object>>[] properties) where T : INotifyPropertyChanged
        {

            if (properties == null || properties.Length == 0)
                throw new ArgumentException("properties must not be empty");

            return properties.Select(p => me.WhenPropertyChanges(p)).Merge();
        }


        /// <summary>
        /// Call a delegate when a particualr property changes
        /// </summary>
        public static IObservable<EventPattern<EventArgs>> WhenDependencyPropertyChanges<T>(this T me, Expression<Func<T, object>> property) where T : DependencyObject
        {
            return FromPropertyChanged(me, property);
        }




        private static IObservable<EventPattern<EventArgs>> FromPropertyChanged<T>(T target, Expression<Func<T, object>> property)
        {
            
            string propertyName = GetNameFromExpression(property);

            var propertyDescriptor = (from p in TypeDescriptor.GetProperties(target).Cast<PropertyDescriptor>()
                                      where string.Equals(p.Name, propertyName, StringComparison.Ordinal)
                                      select p)
                .Single();

            if (!propertyDescriptor.SupportsChangeEvents)
                throw new ArgumentException(string.Format("The specified property '{0}' does not support change events.", propertyName), "property");


            return Observable.FromEventPattern<EventHandler, EventArgs>(
                d => d.Invoke,
                h => propertyDescriptor.AddValueChanged(target, h),
                h => propertyDescriptor.RemoveValueChanged(target, h));
        }

        private static string GetNameFromExpression<T>(Expression<Func<T, object>> property)
        {
            if (property.Body is MemberExpression)
                return GetNameFromMemberExpression(((MemberExpression)property.Body));

            if (property.Body is UnaryExpression)
                return GetNameFromMemberExpression(((UnaryExpression)property.Body).Operand as MemberExpression);

            throw new ArgumentException("Can't find a member expression", "property");
        }

        private static string GetNameFromMemberExpression(MemberExpression expression)
        {
            if (expression == null)
                throw new ArgumentException("The specified expression does not reference a property.", "expression");

            var property = expression.Member as PropertyInfo;
            if (property == null)
                throw new ArgumentException("The specified expression does not reference a property.", "expression");

            return property.Name;
        }



    }
}